package com.zys.http.processor.jaxrs;

import cn.hutool.core.text.CharSequenceUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiSuperMethodImplUtil;
import com.intellij.psi.impl.java.stubs.index.JavaAnnotationIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.zys.http.constant.HttpEnum;
import com.zys.http.entity.param.ParamProperty;
import com.zys.http.entity.tree.MethodNodeData;
import com.zys.http.processor.AbstractJavaProcessor;
import com.zys.http.tool.UrlTool;
import com.zys.http.ui.tree.node.MethodNode;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.jetbrains.annotations.NotNull;

import java.util.*;

/**
 * @author zys
 * @since 2025-01-13
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JaxRsJavaProcessor extends AbstractJavaProcessor {
    public static final JaxRsJavaProcessor INSTANCE = new JaxRsJavaProcessor();

    private static final String CONSUMES_JAVAX = "javax.ws.rs.Consumes";
    private static final String CONSUMES_JAKARTA = "jakarta.ws.rs.Consumes";

    @Override
    public boolean isTargetController(PsiElement element) {
        if (!(element instanceof PsiClass psiClass)) {
            return false;
        }
        return Arrays.stream(psiClass.getAnnotations()).anyMatch(a ->
                isTargetAnnotation(a, JaxRsEnums.Controller.PATH_JAVAX.getClazz()) ||
                        isTargetAnnotation(a, JaxRsEnums.Controller.PATH_JAKARTA.getClazz())
        );
    }

    @Override
    @SuppressWarnings("deprecation")
    public List<? extends PsiElement> moduleControllers(Module module, Project project) {
        Optional<GlobalSearchScope> scopeOp = Optional.of(module).map(Module::getModuleScope);
        Collection<PsiAnnotation> annotations = ApplicationManager.getApplication().runReadAction((Computable<Collection<PsiAnnotation>>) () -> {
            Collection<PsiAnnotation> all = new ArrayList<>();
            for (JaxRsEnums.Controller controller : JaxRsEnums.Controller.values()) {
                String shortClassName = controller.getShortClassName();
                all.addAll(scopeOp.map(ms -> JavaAnnotationIndex.getInstance().get(shortClassName, project, ms)).orElse(new ArrayList<>()));
            }
            return all;
        });

        return annotations.stream()
                .map(PsiElement::getParent)
                .map(PsiModifierList.class::cast)
                .map(PsiModifierList::getParent)
                .filter(PsiClass.class::isInstance)
                .map(PsiClass.class::cast)
                .toList();
    }


    @Override
    public List<String> controllerPaths(PsiElement element) {
        Set<? extends PsiElement> annotations = this.controllerAnnotations(element);

        for (PsiElement annotation : annotations) {
            if (isTargetAnnotation(annotation, JaxRsEnums.Controller.PATH_JAVAX.getClazz()) ||
                    isTargetAnnotation(annotation, JaxRsEnums.Controller.PATH_JAKARTA.getClazz())) {
                return annotationValues(annotation, new String[]{VALUE_KEY});
            }
        }

        return DEFAULT_ANNO_VALUE;
    }


    @Override
    public boolean isTargetMethod(PsiElement element) {
        if (!(element instanceof PsiMethod method)) {
            return false;
        }
        PsiAnnotation[] annotations = method.getAnnotations();
        for (PsiAnnotation annotation : annotations) {
            if (JaxRsEnums.Method.contains(annotation)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public List<String> methodPaths(PsiElement element) {
        if (!(element instanceof PsiMethod method)) {
            return DEFAULT_ANNO_VALUE;
        }
        PsiAnnotation[] annotations = method.getAnnotations();
        for (PsiAnnotation annotation : annotations) {
            if (isTargetAnnotation(annotation, JaxRsEnums.Controller.PATH_JAVAX.getClazz()) ||
                    isTargetAnnotation(annotation, JaxRsEnums.Controller.PATH_JAKARTA.getClazz())
            ) {
                return annotationValues(annotation, new String[]{VALUE_KEY});
            }

        }
        PsiMethod[] superMethods = PsiSuperMethodImplUtil.findSuperMethods(method);
        for (PsiMethod superMethod : superMethods) {
            return methodPaths(superMethod);
        }
        return DEFAULT_ANNO_VALUE;
    }


    @Override
    public List<MethodNode> methodNodeCreate(PsiElement element, String controllerPath, String contextPath, boolean isFirstCall) {
        if (!(element instanceof PsiMethod method)) {
            return Collections.emptyList();
        }

        PsiAnnotation[] annotations = method.getAnnotations();

        PsiAnnotation pathAnno = null;
        PsiAnnotation methodAnno = null;

        for (PsiAnnotation annotation : annotations) {
            if (isTargetAnnotation(annotation, JaxRsEnums.Controller.PATH_JAVAX.getClazz()) ||
                    isTargetAnnotation(annotation, JaxRsEnums.Controller.PATH_JAKARTA.getClazz())
            ) {
                pathAnno = annotation;
            }

            if (JaxRsEnums.Method.contains(annotation)) {
                methodAnno = annotation;
            }
            if (Objects.nonNull(pathAnno) && Objects.nonNull(methodAnno)) {
                break;
            }
        }
        if (Objects.isNull(methodAnno)) {
            return Collections.emptyList();
        }
        String name = annotationValue(pathAnno, new String[]{VALUE_KEY});
        HttpEnum.HttpMethod httpMethod = JaxRsEnums.Method.get(methodAnno);
        MethodNodeData data = new MethodNodeData(httpMethod, name, controllerPath, contextPath);
        data.setDescription(methodSwagger(method));
        data.setPsiElement(method);
        List<MethodNode> res = new ArrayList<>();
        res.add(new MethodNode(data));

        if (isFirstCall) {
            PsiMethod[] superMethods = PsiSuperMethodImplUtil.findSuperMethods(method);
            for (PsiMethod superMethod : superMethods) {
                List<MethodNode> list = methodNodeCreate(superMethod, controllerPath, contextPath, false).stream()
                        .peek(o -> o.getValue().setPsiElement(method)).toList();
                res.addAll(list);
            }
        }
        return res;
    }


    @Override
    public void parameterParse(@NotNull PsiElement element, @NotNull Map<String, ParamProperty> map, boolean isJsonPretty) {
        if (!(element instanceof PsiParameter parameter)) {
            return;
        }

        String parameterName = parameter.getName();
        PsiType parameterType = parameter.getType();

        // 1 如果有 @HeaderParam 注解
        PsiAnnotation anno = parameter.getAnnotation(JaxRsEnums.Param.HEADER_PARAM_JAVAX.getClazz());
        anno = anno == null ? parameter.getAnnotation(JaxRsEnums.Param.HEADER_PARAM_JAKARTA.getClazz()) : anno;

        if (anno != null) {
            parameterName = annotationValue(anno, new String[]{VALUE_KEY});
            map.put(REQUEST_TYPE_KEY, new ParamProperty(HttpEnum.ContentType.APPLICATION_X_FORM_URLENCODED, HttpEnum.ParamUsage.HEADER));
            map.put(parameterName, new ParamProperty("", HttpEnum.ParamUsage.HEADER));
            return;
        }

        // 2 如果有 @QueryParam 注解
        HttpEnum.ParamUsage paramUsage = HttpEnum.ParamUsage.URL;
        anno = parameter.getAnnotation(JaxRsEnums.Param.QUERY_PARAM_JAVAX.getClazz());
        anno = anno == null ? parameter.getAnnotation(JaxRsEnums.Param.QUERY_PARAM_JAKARTA.getClazz()) : anno;
        if (anno != null) {
            parameterName = annotationValue(anno, new String[]{VALUE_KEY});
        }

        // 3 如果有 @PathParam 注解
        anno = parameter.getAnnotation(JaxRsEnums.Param.PATH_PARAM_JAVAX.getClazz());
        anno = anno == null ? parameter.getAnnotation(JaxRsEnums.Param.PATH_PARAM_JAKARTA.getClazz()) : anno;
        if (anno != null) {
            paramUsage = HttpEnum.ParamUsage.PATH;
            parameterName = annotationValue(anno, new String[]{VALUE_KEY});
        }

        Object paramDefaultTypeValue = typeDefaultValue(parameterType, parameter.getProject());
        if (Objects.isNull(paramDefaultTypeValue)) {
            map.put(REQUEST_TYPE_KEY, new ParamProperty(HttpEnum.ContentType.APPLICATION_X_FORM_URLENCODED, HttpEnum.ParamUsage.HEADER));
            return;
        }
        PsiMethod method = (PsiMethod) parameter.getParent().getParent();
        PsiAnnotation annotation = method.getAnnotation(CONSUMES_JAVAX);
        annotation = Objects.isNull(annotation) ? method.getAnnotation(CONSUMES_JAKARTA) : annotation;
        boolean isJson = false;
        if (Objects.nonNull(annotation)) {
            String s = annotationValue(annotation, new String[]{VALUE_KEY});
            isJson = s.contains(HttpEnum.ContentType.APPLICATION_JSON.getValue()) || "MediaType.APPLICATION_JSON".equals(s);
        }

        typeParseResult(parameterName, paramDefaultTypeValue, isJson, isJsonPretty, map, paramUsage);
    }

    @Override
    public List<PostmanItem> postmanMethodItems(PsiElement element, String modulePath, String controllerPath) {
        if (!isTargetMethod(element)) {
            return Collections.emptyList();
        }
        PsiMethod method = (PsiMethod) element;

        // 方法名
        String methodSwagger = methodSwagger(method);
        methodSwagger = CharSequenceUtil.isEmpty(methodSwagger) ? method.getName() : methodSwagger;

        String methodPath = methodPaths(element).get(0);
        HttpEnum.HttpMethod httpMethod = null;
        for (PsiAnnotation annotation : method.getAnnotations()) {
            httpMethod = JaxRsEnums.Method.get(annotation);
            if (httpMethod != null) {
                break;
            }
        }
        httpMethod = httpMethod == null ? HttpEnum.HttpMethod.GET : httpMethod;


        PostmanItem postmanItem = postmanItemCreate(methodSwagger, httpMethod.name(), UrlTool.buildMethodUri(modulePath, controllerPath, methodPath));
        this.postmanItemPropertyCompletion(method, postmanItem);

        return List.of(postmanItem);
    }
}
